//////////////////////////////////////////////////////////////////////
//
//  CryEngine Source code
//	
//	File:DecalManager.cpp
//  Implementation of DecalManager class
//
//	History:
//	August 16, 2004: Created by Ivo Herzeg <ivo@crytek.de>
//
//////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include <CryHeaders.h>

#include "CryEngineDecalInfo.h"
#include "DecalManager.h"
#include "Model.h"
#include "CharacterInstance.h"

static const char* DEFAULT_DECAL_MATERIAL = "Materials/Decals/CharacterDecal";

// the array of transformed vertices (where 0,0,0 is the hit point, and 0,0,1 is the direction of the bullet)
// in other words, it's the Vertices in Bullet Coordinate System
std::vector<Vec3> g_arrVerticesBS;
// the mapping from the character vertices (in the external indexation) to the decal vertices
// -1 means the vertex has not yet been mapped
// It is always true that an element is >= -1 and < m_arrDecalVertices.size()
std::vector<int32> g_arrDecalVertexMapping;
std::vector<int32> g_arrDecalFaceMapping;



//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
CAnimDecalManager::CAnimDecalManager() :
	m_pDefaultDecalMaterial(NULL)
{
}

size_t CAnimDecalManager::SizeOfThis ()
{
	size_t DecalInfoMem = sizeofVector(m_arrDecalRequests);
	size_t DecalsMem=0;
	size_t numDecals = m_arrDecals.size();
	for (uint32 i=0; i<numDecals; i++)
		DecalsMem	+=	m_arrDecals[i].GetSize();

	return DecalInfoMem+DecalsMem;
}

void CAnimDecalManager::GetMemoryUsage(ICrySizer *pSizer) const
{
	pSizer->AddObject(m_arrDecalRequests);
	pSizer->AddObject(m_arrDecals);
}

void CAnimDecalManager::SetDefaultDecalMaterial()
{
	IMaterialManager* pMaterialManager = g_pI3DEngine->GetMaterialManager();
	m_pDefaultDecalMaterial = pMaterialManager->FindMaterial(DEFAULT_DECAL_MATERIAL);

	if (!m_pDefaultDecalMaterial) 
	{
		m_pDefaultDecalMaterial = pMaterialManager->LoadMaterial(DEFAULT_DECAL_MATERIAL, true, true);
		if (!m_pDefaultDecalMaterial) 
			m_pDefaultDecalMaterial = pMaterialManager->GetDefaultMaterial();
	}
}

//----------------------------------------------------------------------------------
//-------     Spawn decal on the base-model and on all slave-model           -------
//----------------------------------------------------------------------------------
void CAnimDecalManager::CreateDecalsOnInstance(QuatTS* parrNewSkinQuat, CCharacterModel* pModel, CMorphing* pMorphing, f32* pShapeDeform, CryEngineDecalInfo& DecalLCS, const QuatT& rPhysEntityLocation )
{
	CModelMesh* pMesh = pModel->GetModelMesh(pModel->m_nBaseLOD);

	if (!pMesh)
		return;

	FUNCTION_PROFILER( gEnv->pSystem, PROFILE_ANIMATION );

	// The decal info in the local coordinate system
	CryEngineDecalInfo DecalLocal = DecalLCS;
	DecalLocal.fSize *= Console::GetInst().ca_DecalSizeMultiplier * float(0.4 + (rand()&0x7F)*0.001953125);
	DecalLocal.fAngle = (rand()&0xFF)*float(2*gf_PI/256.0f);
	m_arrDecalRequests.push_back(DecalLocal);

	OBB obb;
	const uint32 count = pModel->m_arrCollisions.size();
	for (uint32 i=0; i<count; i++)
	{
		int32 id = pModel->m_arrCollisions[i].m_iBoneId;
		AABB aabb	=	pModel->m_arrCollisions[i].m_aABB;
  	if( !aabb.IsReset() )
   	{
			const QuatD& qd=(const QuatD&)parrNewSkinQuat[id].q;
			QuatTS wjoint = rPhysEntityLocation * QuatT(qd);
			obb.SetOBBfromAABB(wjoint.q, aabb);
  		pModel->m_arrCollisions[i].m_OBB = obb;
			pModel->m_arrCollisions[i].m_Pos = wjoint.t;
   	}
	}
		
	const uint32 numIntVertices	= pMesh->GetVertextCount();
	const uint32 numIntFaces		= pMesh->m_numExtTriangles;
	const uint32 numVMappings		= g_arrDecalVertexMapping.size();

	if (numVMappings < numIntVertices)
		g_arrDecalVertexMapping.resize(numIntVertices);

	const uint32 numVerticesBCS = g_arrVerticesBS.size();
	if (numVerticesBCS < numIntVertices)
		g_arrVerticesBS.resize(numIntVertices);

	if (g_arrDecalFaceMapping.size() < numIntFaces* 3)
		g_arrDecalFaceMapping.resize(numIntFaces * 3);

	bool bFirstLock = true;

	// if there are decal requests, then converts them into the decal objects
	// reserves the vertex/updates the index buffers, if need to be
	// sets m_bNeedUpdateIndices to true if it has added something (and therefore refresh of indices is required)
	const uint32 numDecalRequests = m_arrDecalRequests.size();

	// realize each unrealized decal, then clean up the array of unrealized decals
	for (uint32 nDecal=0; nDecal<numDecalRequests; ++nDecal)
	{
		CryEngineDecalInfo& rDecalInfo = m_arrDecalRequests[nDecal];
		CAnimDecal NewDecal;
		bool bFirstMaterial = true;
		assert((fabs_tpl(1-(rDecalInfo.vHitDirection|rDecalInfo.vHitDirection)))<0.0001);		//check if unit-vector

		// transform the bullet position to the local coordinates
		NewDecal.m_ptSource = rDecalInfo.vPos - rPhysEntityLocation.t;

		// Z axis looks in the direction of the hit
		Matrix34 m_matBullet34 = Matrix33::CreateRotationV0V1(Vec3(0,0,-1),rDecalInfo.vHitDirection);
		m_matBullet34.SetTranslation(NewDecal.m_ptSource);

		m_matBullet34.m02 *= 4.0f;
		m_matBullet34.m12 *= 4.0f;
		m_matBullet34.m22 *= 4.0f;

		const Matrix34 m_matInvBullet34 = m_matBullet34.GetInverted();

		memset(&g_arrDecalVertexMapping[0],-1, g_arrDecalVertexMapping.size() * sizeof(int));
		memset(&g_arrDecalFaceMapping[0],-1, g_arrDecalFaceMapping.size() * sizeof(int));

		Vec3 vec;
		const uint32 numCollisions = pModel->m_arrCollisions.size();
		for (uint32 bb=0; bb<numCollisions; ++bb)
		{
			const MeshCollisionInfo collisionInfo = pMesh->m_pModel->m_arrCollisions[bb];

			if (Intersect::Ray_OBB(Ray(rDecalInfo.vPos, rDecalInfo.vHitDirection), collisionInfo.m_Pos, collisionInfo.m_OBB, vec) != 0)
			{
				if (bFirstLock) 
				{
					pMesh->InitSkinningExtSW(pMorphing,pShapeDeform,pModel->m_nBaseLOD);
					pMesh->LockFullRenderMesh(pModel->m_nBaseLOD);
					bFirstLock = false;
				}

				if (bFirstMaterial) 
				{
					if (rDecalInfo.szMaterialName[0] != 0) 
					{
						IMaterialManager* pMatMan = g_pI3DEngine->GetMaterialManager();

						NewDecal.m_pMaterial = pMatMan->FindMaterial(rDecalInfo.szMaterialName);
						if (!NewDecal.m_pMaterial)
							NewDecal.m_pMaterial = pMatMan->LoadMaterial(rDecalInfo.szMaterialName, true, true);
					} 
					else 
					{
						if (!m_pDefaultDecalMaterial)
							SetDefaultDecalMaterial();
											
						NewDecal.m_pMaterial = m_pDefaultDecalMaterial;
					}
				}

				const Matrix34 m34 = m_matInvBullet34 * Matrix33(rPhysEntityLocation.q);	
				Vec3 tmp, tmpn;
				for (uint32 v=0, end = collisionInfo.m_arrIndexes.size(); v<end; v++)
				{
					const int ind = collisionInfo.m_arrIndexes[v];

					if((ind*3+2) >= pMesh->m_Indices)
						CryFatalError("Out of bounds index - %d of %d in %s", ind*3 + 2, pMesh->m_Indices, pModel->GetModelFilePath());
					
					const int i0 = pMesh->m_pIndices[ind*3+0];
					const int i1 = pMesh->m_pIndices[ind*3+1];
					const int i2 = pMesh->m_pIndices[ind*3+2];
					g_arrVerticesBS[i0] = m34 * pMesh->GetSkinnedExtVertex2(parrNewSkinQuat, i0);
					g_arrVerticesBS[i1] = m34 * pMesh->GetSkinnedExtVertex2(parrNewSkinQuat, i1);
					g_arrVerticesBS[i2] = m34 * pMesh->GetSkinnedExtVertex2(parrNewSkinQuat, i2);
				}

				// find the vertices that participate in decal generation and add them to the array of vertices
				//for (uint32 i=0; i<numIntVertices; i++)
				//	g_arrDecalVertexMapping[i]=-1;


				// FIXME: [CarstenW & MichaelR]: TexScale was always set to 0.030f regardless of fSize.
				// Changed this to allow decals on large characters (on hunter the Overlap tests failed), 
				// without messing up appearance of currently used smaller ones (e.g. bullet decals)    
				const f32 TexScale = (rDecalInfo.fSize < 0.25f) ? 0.030f : rDecalInfo.fSize;
				//TexScale = 0.030f; 

				// find the faces to which the distance near enough
				for (uint32 nFace=0, end = collisionInfo.m_arrIndexes.size() ; nFace<end; ++nFace)
				{
					// vertices belonging to the face, in internal indexation
					const int ind = collisionInfo.m_arrIndexes[nFace];
					if (g_arrDecalFaceMapping[ind] != -1)
						continue;
					
					g_arrDecalFaceMapping[ind] = 0;

					const TFace Face(pMesh->m_pIndices[ind*3+0], pMesh->m_pIndices[ind*3+1], pMesh->m_pIndices[ind*3+2]);
					const	Triangle tri(g_arrVerticesBS[Face.i0], g_arrVerticesBS[Face.i1], g_arrVerticesBS[Face.i2]);
					
					//backface test
					const f32 b0 =  (tri.v1.x-tri.v0.x) * (tri.v2.y-tri.v0.y) - (tri.v2.x-tri.v0.x) * (tri.v1.y-tri.v0.y);
					if (b0>0) 
					{
						const uint32 t = Overlap::Sphere_Triangle(Sphere(Vec3(ZERO),TexScale), tri);

						if (t)
						{
							CDecalVertex DecalVertex;
							const f32 ts = 1.0f/TexScale;
							uint32 i0,i1,i2;

							if (g_arrDecalVertexMapping[Face.i0] != -1)	
							{	
								i0=g_arrDecalVertexMapping[Face.i0]; 
							}
							else 
							{
								DecalVertex.nVertex = Face.i0; 
								DecalVertex.u	= 0.5f+(g_arrVerticesBS[Face.i0].x*ts); 
								DecalVertex.v	=	0.5f+(g_arrVerticesBS[Face.i0].y*ts);
								
								NewDecal.m_arrVertices.push_back(DecalVertex);
								g_arrDecalVertexMapping[Face.i0] = int(NewDecal.m_arrVertices.size()-1);
								i0=g_arrDecalVertexMapping[Face.i0];
							}

							if (g_arrDecalVertexMapping[Face.i1] != -1)	
							{ 
								i1=g_arrDecalVertexMapping[Face.i1];	
							}
							else 
							{
								DecalVertex.nVertex = Face.i1; 
								DecalVertex.u	= 0.5f+(g_arrVerticesBS[Face.i1].x*ts); 
								DecalVertex.v	=	0.5f+(g_arrVerticesBS[Face.i1].y*ts);
								
								NewDecal.m_arrVertices.push_back(DecalVertex);
								g_arrDecalVertexMapping[Face.i1] = int(NewDecal.m_arrVertices.size()-1);
								i1=g_arrDecalVertexMapping[Face.i1];
							}

							if (g_arrDecalVertexMapping[Face.i2] != -1)	
							{ 
								i2=g_arrDecalVertexMapping[Face.i2];
							}
							else 
							{
								DecalVertex.nVertex = Face.i2; 
								DecalVertex.u	= 0.5f+(g_arrVerticesBS[Face.i2].x*ts); 
								DecalVertex.v	=	0.5f+(g_arrVerticesBS[Face.i2].y*ts);
								
								NewDecal.m_arrVertices.push_back(DecalVertex);
								g_arrDecalVertexMapping[Face.i2] = int(NewDecal.m_arrVertices.size()-1);
								i2=g_arrDecalVertexMapping[Face.i2];
							}

							NewDecal.m_arrFaces.push_back(TFace(i0,i1,i2));
						}
					}
				}
			}
		}

		if (NewDecal.m_arrFaces.empty())
			continue; // we're not interested in this decal: we don't have any decals

		NewDecal.m_fBuildTime		= g_fCurrTime;
		NewDecal.m_fFadeInTime	= rDecalInfo.fSize / (0.04f + 0.02f*(rand()%0xFF)/256.0f); // suppose the speed is like this
		NewDecal.m_fDeathTime		= NewDecal.m_fBuildTime + rDecalInfo.fLifeTime; //remove decal after 180 seconds
		NewDecal.m_fFadeOutTime = -1; // no fade out
		m_arrDecals.push_back(NewDecal);
	}

	if (!bFirstLock)
		pMesh->UnlockFullRenderMesh(pMesh->m_pModel->m_nBaseLOD);

	// after we realized the decal request, we don't need it anymore
	m_arrDecalRequests.clear();
}

// starts fading out all decals that are close enough to the given point
// NOTE: the radius is m^2 - it's the square of the radius of the sphere
void CAnimDecalManager::fadeOutCloseDecals (const Vec3& ptCenter, float fRadius2)
{
	uint32 numDecals = m_arrDecals.size();
	for (uint32 i=0; i<numDecals; ++i)
	{
		if ( (m_arrDecals[i].m_ptSource-ptCenter).GetLengthSquared() < fRadius2)
			m_arrDecals[i].startFadeOut (2);
	}
}

// if we delete one of the decals
void CAnimDecalManager::RemoveObsoleteDecals()
{
	uint32 numDecals = m_arrDecals.size();
	for (uint32 i=0; i<numDecals; i++)
	{
		if ( m_arrDecals[i].isDead() )
		{
			m_arrDecals[i] = m_arrDecals[numDecals-1];
			m_arrDecals.pop_back();
			numDecals--;
		}
	}
}

// cleans up all decals, destroys the vertex buffer
void CAnimDecalManager::clear()
{
	m_arrDecalRequests.clear();
	m_arrDecals.clear();
}

// returns the decal multiplier: 0 - no decal, 1 - full decal size
f32 CAnimDecal::getIntensity()const
{
	if (g_fCurrTime >= m_fDeathTime)
		// we've faded out
		return 0;

	if (g_fCurrTime > m_fDeathTime - m_fFadeOutTime)
		// we're fading out
		return 1-sqr(1-(m_fDeathTime - g_fCurrTime) / m_fFadeOutTime);


	f32 fLifeTime = (g_fCurrTime - m_fBuildTime);
	if (fLifeTime > m_fFadeInTime)
		// we've faded in
		return 1;
	else
		// we're fading in
		return 1 - sqr(1 - fLifeTime / m_fFadeInTime);
}

// returns true if this is a dead/empty decal and should be discarded
bool CAnimDecal::isDead()
{
	return g_fCurrTime >= m_fDeathTime;
}

// starts fading out the decal from this moment on
void CAnimDecal::startFadeOut(f32 fTimeout)
{
	m_fFadeOutTime = fTimeout;
	m_fDeathTime = g_fCurrTime + fTimeout;
}
